package com.example.uploadfile.service;

import com.example.uploadfile.pojo.UploadFileBO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

@Service
@Slf4j
public class UploadFileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(UploadFileService.class);
    private static final String PATH_SYSTEM_TEMP = System.getProperty("java.io.tmpdir");;
    private static final String PATH_SYSTEM_TARGET = "D:\\workspaces\\upload-file\\target";
    private static final String PATH_SYSTEM_UNZIP = "D:\\workspaces\\upload-file\\unzip";
    private static final String PATH_PROJECT_TARGET = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    private static final ExecutorService threadPool = Executors.newCachedThreadPool();

    @Autowired
    private RestTemplate restTemplate;

    public Boolean uploadFile(UploadFileBO uploadFileBO) {
        MultipartFile multipartFile = uploadFileBO.getMultipartFile();
        String filename = multipartFile.getOriginalFilename();
        int taskType = uploadFileBO.getTaskType();
        System.out.println("filename :" + filename);

        if (!uploadFileToTemp(multipartFile, filename)) {
            return Boolean.FALSE;
        }

//        if (!uploadFileToTemp2(multipartFile, filename)) {
//            return Boolean.FALSE;
//        }

//        if (!uploadFileToTemp3(multipartFile, filename)) {
//            return Boolean.FALSE;
//        }

        if (!copyToSystemTarget(filename)) {
            return Boolean.FALSE;
        }

        File systemTarget = new File(PATH_SYSTEM_TARGET + File.separator + filename);
        LOGGER.info("systemTarget : [{}]", systemTarget);
        if (!unZip(systemTarget, PATH_SYSTEM_UNZIP + File.separator + uploadFileBO.getBatchId())) {
            return Boolean.FALSE;
        }

        if (!unZip(systemTarget, PATH_PROJECT_TARGET + File.separator + uploadFileBO.getBatchId())) {
            return Boolean.FALSE;
        }

        // 要有转发停止的标识，不然会无线循环
        // 如果是分片任务，情况会更复杂点
//        if (taskType == 0) {
//            forwardRequestAsync(systemTarget);
//        }

        return Boolean.TRUE;

    }

    private Boolean uploadFileToTemp2(MultipartFile multipartFile, String filename) {
        try {
            String target = PATH_SYSTEM_TEMP + File.separator + filename;
            multipartFile.transferTo(new File(target));
        } catch (IOException e) {
            LOGGER.error("uploadFileToTemp2 error...", e);
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    private Boolean uploadFileToTemp3(MultipartFile multipartFile, String filename) {
        try {
            String target = PATH_SYSTEM_TEMP + File.separator + filename;
            Files.copy(multipartFile.getInputStream(), new File(target).toPath());
        } catch (IOException e) {
            LOGGER.error("uploadFileToTemp3 error...", e);
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    private Boolean uploadFileToTemp(MultipartFile multipartFile, String filename) {
        OutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            File tempFile = new File(PATH_SYSTEM_TEMP);
            System.out.println("tempFile :" + tempFile);
            if (!tempFile.exists()) {
                tempFile.mkdirs();
            }
            inputStream = multipartFile.getInputStream();
            FileOutputStream fileOutputStream = new FileOutputStream(tempFile + File.separator + filename);
            Boolean isCompleteFile = Boolean.TRUE;
            if (isCompleteFile) {
//                outputStream  = new FileOutputStream(tempFile + File.separator + filename);
                outputStream = new BufferedOutputStream(fileOutputStream);
//                outputStream  = new ByteArrayOutputStream(tempFile + File.separator + filename);
            } else {
                outputStream  = new FileOutputStream(tempFile + File.separator + filename,
                        Boolean.TRUE);
            }
            byte[] buffer = new byte[1024 * 10];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            outputStream.flush();
        } catch (IOException e) {
            LOGGER.error("uploadFileToTemp error...", e);
            return Boolean.FALSE;
        } finally {
            IOUtils.closeQuietly(outputStream);
            IOUtils.closeQuietly(inputStream);
        }
        return Boolean.TRUE;
    }

    public Boolean copyToSystemTarget(String fileName) {
        try {
            File targetFile = new File(PATH_SYSTEM_TARGET);
            if (!targetFile.exists()) {
                targetFile.mkdirs();
            }
            Path sourcePat = new File(PATH_SYSTEM_TEMP + File.separator + fileName).toPath();
            Path targetPath = new File(PATH_SYSTEM_TARGET + File.separator + fileName).toPath();
            Files.deleteIfExists(targetPath);
            Files.copy(sourcePat, targetPath);
        } catch (IOException e) {
            LOGGER.error("copyToSystemTarget error...", e);
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    public Boolean unZip(File srcFile, String destDirPath) throws RuntimeException {
        System.out.println("unZip destDirPath :" + destDirPath);
        long start = System.currentTimeMillis();
        // 判断源文件是否存在
        if (!srcFile.exists()) {
            throw new RuntimeException(srcFile.getPath() + "所指文件不存在");
        }
        // 开始解压
        ZipFile zipFile = null;
        try {
            FileUtils.cleanDirectory(new File(destDirPath));
            zipFile = new ZipFile(srcFile);
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                System.out.println("解压" + entry.getName());

                // 如果是文件夹，就创建个文件夹
                if (entry.isDirectory()) {
                    String dirPath = destDirPath + "/" + entry.getName();
                    File dir = new File(dirPath);
                    dir.mkdirs();
                } else {
                    // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
                    File targetFile = new File(destDirPath + "/" + entry.getName());
                    // 保证这个文件的父文件夹必须要存在
                    if(!targetFile.getParentFile().exists()){
                        targetFile.getParentFile().mkdirs();

                    }
                    targetFile.createNewFile();

                    // 将压缩文件内容写入到这个文件中
                    InputStream is = zipFile.getInputStream(entry);
                    FileOutputStream fos = new FileOutputStream(targetFile);
                    int len;
                    byte[] buf = new byte[1024];

                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);

                    }
                    // 关流顺序，先打开的后关闭
                    fos.close();
                    is.close();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("解压完成，耗时：" + (end - start) +" ms");
        } catch (Exception e) {
            throw new RuntimeException("unzip error from ZipUtils", e);
        } finally {
            if(zipFile != null){
                try {
                    zipFile.close();
                } catch (IOException e) {
                    LOGGER.error("unZip close error...", e);
                }
            }
        }

        return Boolean.TRUE;
    }

    public void forwardRequestAsync(File sourceFile){
        threadPool.execute(() -> forwardRequest(sourceFile));
    }

    public void forwardRequest(File sourceFile) {
        try {
            String url = "http://localhost:8081/upload-file/upload/file2";
            MultipartFile multipartFile = getMultipartFile(sourceFile);

            ByteArrayResource fileAsResource = new ByteArrayResource(multipartFile.getBytes()) {
                @Override
                public String getFilename() {
                    return multipartFile.getOriginalFilename();
                }

                @Override
                public long contentLength() {
                    return multipartFile.getSize();
                }
            };

            /**
             设置请求头为multipart/form-data
             */
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);

            //创建一个参数map，添加参数
            MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
            params.add("multipartFile", fileAsResource);
            params.add("batchId", "forward-123");

            //把参数和请求头放到请求体中
            HttpEntity<Object> request = new HttpEntity<>(params, headers);
            //请求接口
            ResponseEntity<Object> objectResponseEntity = restTemplate.postForEntity(url, request, Object.class);
            Boolean body = (Boolean)objectResponseEntity.getBody();
            LOGGER.info("forwardRequest result : [{}]", body);
        } catch (Exception e) {
            LOGGER.error("forwardRequest error...", e);
        }
    }

    private MultipartFile getMultipartFile(File file) {
        FileItem item = new DiskFileItemFactory().createItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName());
        try (InputStream input = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            // 流转移
            IOUtils.copy(input, os);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid file: " + e, e);
        }
        return new CommonsMultipartFile(item);
    }

}
